﻿package net.yogadigital.popit.core 
{
	import com.flashdynamix.motion.TweensyTimeline;
	import fl.motion.easing.Linear;
	import fl.motion.easing.Quintic;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import com.flashdynamix.motion.Tweensy;
	import net.yogadigital.popit.events.PopitEvent;
	import net.yogadigital.popit.PopitManager;
	
	import br.com.stimuli.loading.BulkLoader;
	import br.com.stimuli.loading.BulkProgressEvent;
	
	import flash.net.LocalConnection;
	import flash.system.System;
	
	/**
	 * Base Element. You can extend this element in order to build more complex windows (adding visual effects or so)
	 * @author Juanda
	 */
	public class PopitWindow extends Sprite
	{	
		//When the object pool is full, we need to delete the windows and popups that 
		//are least used, not the most used.
		protected var _timesOpened:Number = 0;
		protected var _isClosed:Boolean = true;

		protected var _mainTween:TweensyTimeline;
		protected var _backgroundTween:TweensyTimeline;
		protected var _container:DisplayObjectContainer;
		protected var _deemedBackground:Sprite;
		protected var _popitManager:PopitManager = PopitManager.getInstance();
		
		protected const DEEMED_BACKGROUND_NAME:String = "DeemedBackgroundName_popit";
		
		//If the elements are already loaded you cannot call the Load method again
		private var _assetsLoaded:Boolean = false;
		
		/**
		 * The Sprite representing the content of the element. It has to be a SWF file with
		 * the close button defined. That close button cannot be nested within other elements and has 
		 * to be named <strong>"PopitCloseButton"</strong>
		 */
		protected var _content:Sprite;
		
		/**
		 * The bulk loader of the element. It handles the loading of all the assets
		 */
		protected var _loader:BulkLoader;
		
		/**
		 * The URL of the external content that represents the PopitWindow
		 */
		protected var _contentURL:String;
		
		/**
		 * The constructor of a Popit element.
		 * @param	name
		 */
		public function PopitWindow(oName:String, container:DisplayObjectContainer, contentURL:String)
		{
			name = oName;
			_contentURL = contentURL;
			_container = container;
			_loader = new BulkLoader(name + "bulkLoader_popit");
		}
		
		/**
		 * It loads all the external assets of the element.
		 * As soon as the load is completed, it dispatches a LOAD_COMPLETED event, so the
		 * element can be properly displayed.
		 * 
		 * Once all the assets are loaded you can call the method "show" in order to
		 * show the current element
		 * 
		 * It uses the BulkLoader library to load all the elements.
		 * 
		 * @param	contentURL			The URL of the asset that represents the content of the element
		 * 
		 * @throws Error					If the load method is called more than once, it throws an error. This is
		 * because the bulkloader listeners are disposed once the loading process is completed.
		 * 
		 * The content element has to be an SWF with at least one close button defined. If not, then the
		 * window will not be able to close itself.
		 * The only thing needed to setup a close button is to configure that sprite to Export as:
		 * net.yogadigital.popit.utils.CloseButton, that's it!
		 * 
		 * If there's any error locating the assets (for example: the asset doesn't exists) it skips the file.
		 */
		private function load():void
		{
			if (_assetsLoaded)
			{
				throw new Error("You already loaded the assets for this element and the loader is already disposed! You can only do it once!");
			}
			else
			{				
				_assetsLoaded = true;
				
				_loader.add(_contentURL, {id:"content"});
				_loader.addEventListener(BulkLoader.COMPLETE, onLoadingCompleted, false, 0, true);
				_loader.addEventListener(BulkLoader.PROGRESS, onLoadingProgress, false, 0, true);

				_loader.start();
			}
		}
		
		/**
		 * TODO: Documentation
		 * @param	event
		 */
		private function onLoadingProgress(e:BulkProgressEvent):void 
		{
			trace("\t>>onQueueProgress: " + e.percentLoaded);
		}

		/**
		 * TODO: Documentation
		 * @param	event
		 */
		private function onLoadingCompleted(e:Event):void 
		{
			_content = _loader.getSprite("content", true);

			//Display the loaded content
			addChild(_content);
			
			//Remove the event listeners of the queue
			_loader.removeEventListener(BulkProgressEvent.PROGRESS, onLoadingProgress);
			_loader.removeEventListener(BulkProgressEvent.COMPLETE, onLoadingCompleted);
			
			//Free resources
			_loader.clear();
			_loader = null;
			
			//Notify that the element is completely loaded
			dispatchEvent(new PopitEvent(PopitEvent.ELEMENT_LOADED, true));
			
			//Add the listener to the close button and the drag area
			_content.addEventListener(PopitEvent.CLOSE_BUTTON_PRESSED, close, false, 0, true);
			_content.addEventListener(PopitEvent.START_DRAG, onStartDrag, false, 0, true);
			_content.addEventListener(PopitEvent.STOP_DRAG, onStopDrag, false, 0, true);
		}
		
		/**
		 * Stops dragging the current window
		 * @param	e
		 */
		private function onStopDrag(e:PopitEvent):void 
		{
			stopDrag();
		}
		
		/**
		 * Kills all the Tweens and starts dragging the current window
		 * @param	e
		 */
		private function onStartDrag(e:PopitEvent):void 
		{
			killAllTweens();
			startDrag();
		}
		
		/**
		 * Kills all the tweens for the current window
		 */
		private function killAllTweens():void
		{
			if (_backgroundTween) Tweensy.remove(_backgroundTween);
			if (_mainTween) Tweensy.remove(_mainTween);
		}
		
		/**
		 * Shows the current Window in the las known position.
		 * If you want to show a centered window use the showCentered() method instead
		 */
		public function show():void
		{
			if (_timesOpened == 0)
			{
				//This is the firs time that the window is showing up, so we have
				//to call the load method first
				load();
			}
			else
			{
				//Retrieve the object form the pool
			}
			_timesOpened++;
			trace("Show me");
			_container.addChild(this);
			//TODO: Show the window
		}
		
		/**
		 * Shows the current Window in the center of its container
		 * If you want to show the window in the last know position, use the show() method instead.
		 */
		public function showCentered():void
		{
			if (_timesOpened == 0)
			{
				//This is the firs time that the window is showing up, so we have
				//to call the load method first
				load();
			}
			else
			{
				//Retrieve the object form the pool
			}
			_timesOpened++;
			trace("Show me");
			center();
			_container.addChild(this);
		}
		
		/**
		 * It closes the Window. By closing a window you are not removing it from the object pool, you are
		 * just removing it from the container. If you want to free resources you have to call the
		 * destroy method.
		 * 
		 * It fades out the window along with any deemed backgrounds in it.
		 * @param	e
		 */
		public function close(e:Event):void
		{
			//Fades out the element
			killAllTweens();
			
			_mainTween = Tweensy.to(this, { alpha: 0 }, 0.5, Linear.easeNone, 0, null, closeAllElements);
			if (_deemedBackground)
			{
				_backgroundTween = Tweensy.to(_deemedBackground, { alpha: 0 }, 0.5, Linear.easeNone, 0, null);
			}
			
			//TODO: Dispatch the colsed event and the Popit manager removes it from the display list
		}
		
		/**
		 * Sets up the visible property of the window to false, by doing so, the window doesn't obstruct
		 * the user interaction with the rest of the movie.
		 */
		private function closeAllElements():void
		{
			this.visible = false;
			if(_deemedBackground) _deemedBackground.visible = false;
			destroy();
		}
		
		/**
		 * Centers the current element in his parent container
		 */
		public function center():void
		{
			if (_content)
				simpleMoveTo(new Point(Math.abs((_container.width * 0.5) - _content.width), Math.abs(	(_container.height * 0.5) - _content.height)));
			else
				simpleMoveTo(new Point(Math.abs((_container.width * 0.5) - width), Math.abs(	(_container.height * 0.5) - height)));
		}
		
		/**
		 * Moves the current element to a new position. It doesn't make any nifty animations in the process.
		 * It's just a hard move.
		 * @param	newPosition	The new position of the element
		 */
		public function spawn(newPosition:Point):void
		{
			killAllTweens();
			x = newPosition.x;
			y = newPosition.y;
		}
		
		/**
		 * Animates the current element to a given position using the Tweensy engine.
		 * @param	newPosition				The destination position of the element.
		 * @param	duration					The duration of the animation in seconds.
		 * @param	easing						The easing function to be used. See fl.motion.easing.
		 * @param	delay							The delay before the animation starts.
		 * @param	onComplete				The name of the function to call when the animation is completed.
		 * @param	onCompleteParams	An array with the params that should be passed to the function executed when the animation is completed.
		 */
		public function moveTo(newPosition:Point, duration:Number = 1, easing:Function = null, delay:Number = 0, onComplete:Function = null, onCompleteParams:Array = null):void
		{
			if (_mainTween) Tweensy.remove(_mainTween);
			Tweensy.to(this, {x:newPosition.x, y:newPosition.y}, duration, easing, delay, null, onComplete, onCompleteParams);
		}
		
		/**
		 * Moves the window with a simple movement using the Tweensy engine
		 * @param	newPosition				The destination position of the element.
		 * @param	duration					The duration of the animation in seconds.
		 */
		public function simpleMoveTo(newPosition:Point, duration:Number = 1):void
		{
			if (_mainTween) Tweensy.remove(_mainTween);
			Tweensy.to(this, {x:newPosition.x, y:newPosition.y}, duration);
		}
		
		/**
		 * Affects the background of the popup (Parent). It can create a deemed background effect or add a nifty Perlin Noise
		 * effect to the background
		 * @param	deemColor	 		The color of the deemed background
		 * @param	deemAlpha			The desired alpha of the deemed background
		 * @param	perlinNoise		Wheather to set or not the Perilin Noise effect to the background
		 */
		public function deemTheBackground(deemColor:uint = 0x000000, deemAlpha:Number = 0.7, perlinNoise:Boolean = false):void
		{
			//If there's a deemed background already, then delete if first
			if (!_deemedBackground)
			{				
				//Create shape in top of the container and set the properties
				_deemedBackground = new Sprite();
				_deemedBackground.name = DEEMED_BACKGROUND_NAME;
				_deemedBackground.graphics.beginFill(deemColor);
				_deemedBackground.graphics.drawRect(0, 0, parent.width, parent.height);
				_deemedBackground.graphics.endFill();
				_deemedBackground.x = 0;
				_deemedBackground.y = 0;
				_deemedBackground.alpha = 0;
				
				if (_backgroundTween) Tweensy.remove(_backgroundTween);
				_backgroundTween = Tweensy.to(_deemedBackground, { alpha: deemAlpha }, 0.5);
				
				_container.addChild(_deemedBackground);
				
				//Now swap the depths so the Dialog stands at the top
				_container.swapChildren(this, _container.getChildByName(DEEMED_BACKGROUND_NAME));
			}
		}
		
		/**
		 * Frees all the loaded elements from memory.
		 * In order to properly free the resources, you must remove any event listeners
		 * that you have to the window.
		 * 
		 * Clears all the tweens
		 * Clears all the bulkLoaders
		 * Removes the event listeners to the content
		 * Removes the content
		 * Manually Calls the garbage collector
		 * Dispatches a Destroyed event
		 */
		private function destroy():void
		{
			_container.removeChild(this);
			
			//Clear Tweens
			Tweensy.stopAll();
			Tweensy.empty();
			_mainTween = null;
			_backgroundTween = null;
			
			//Clears the background if there's any
			if (_deemedBackground)
			{
				_container.removeChild(_deemedBackground);
				_deemedBackground = null;
			}
			
			//Remove event listeners
			_content.removeEventListener(PopitEvent.CLOSE_BUTTON_PRESSED, close);
			_content.removeEventListener(PopitEvent.START_DRAG, onStartDrag);
			_content.removeEventListener(PopitEvent.STOP_DRAG, onStopDrag);
			
			//Nullify all the objects
			removeChild(_content);
			_content = null;
			
			//Call de Garbage Collector using the Local connection hack
			//http://www.gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
			//It shouldn't be used in production code, only in development mode
			//TODO: Delete this chunk of code.
			try 
			{
				System.gc();
				new LocalConnection().connect('foo');
				new LocalConnection().connect('foo');
			} 
			catch (e : Error) {}
			
			//Dispatch Destroyed Event
			//The Popit manager gets the event and then removes the object from the pool
		}
	}
}